#include "vm.hpp"
#include "value.hpp"
#include "instr.hpp"
#include "exception.hpp"
#include "vm_func.hpp"
#include "actor.hpp"
#include <stdexcept>
#include <sstream>
#include <iostream>
#include <algorithm>
#include <ctime>

/**
 * \namespace BMD
 * namespace containing the entire BMD runtime system
 */

using std::out_of_range;
using std::ostringstream;
using std::cout;
using std::cerr;
using std::endl;

namespace BMD
{
  const unsigned int VM::RETURN_ADDRESS_SIZE = 2;

  VM::VM(Actor* _parent_actor) : parent_actor(_parent_actor), debug_output(false)
  {
    reset();

    // VM functions need to be registered here
    vm_function_map["to_string"] = to_string;
    vm_function_map["parseint"] = parse_int;
    vm_function_map["parsefloat"] = parse_float;
    vm_function_map["string_length"] = string_length;
    vm_function_map["array_length"] = array_length;
    vm_function_map["rand"] = random_float;
    vm_function_map["print"] = print;
    vm_function_map["print_error"] = print_error;
    vm_function_map["println"] = println;
  }

  /**
   * Resets the running parameters of the virtual machine.
   * The function does not change the active actor. 
   */
  void VM::reset()
  {
    stack.clear();
    //acc = Value(0);
    //acc = shared_ptr<Value>((Value*)NULL);
    curr_sfp = -RETURN_ADDRESS_SIZE;
    ip = 0;
    running = false;
  }

  //! retrieve the value of the accumulator
  Value VM::get_accumulator() const
  {
    return acc;
  }

  //! set the value of the accumulator
  void VM::set_accumulator(Value v)
  {
    acc = v;
  }

  //! pushes the accumulator onto the stack
  void VM::push()
  {
    stack.push_back(acc);
  }

  //! push the value given onto the stack
  void VM::push(Value vr)
  {
    stack.push_back(vr);
  }

  /** Pop the top element off the stack, returning that element. */
  Value VM::pop()
  {
    if (stack.size() <= (unsigned int)(curr_sfp + RETURN_ADDRESS_SIZE))
      throw out_of_range("Cannot pop past the stack frame");

    Value vr = stack.back();
    stack.pop_back();
    return vr;
  }

  /** Pop the given number of entries off the top of 
      the stack. The function throws an exception if
      one tries to pop past the current stack frame. */
  void VM::pop(unsigned int num_entries)
  {
    int new_size = stack.size() - num_entries;
    if (new_size < 0 || (unsigned int)new_size < (curr_sfp + RETURN_ADDRESS_SIZE))
      throw out_of_range("Cannot pop past the stack frame");

    stack.resize(new_size);
  }

  /** Pop the given number of entries off the top of 
      the stack. The function ignores the stack frame, only
      throwing an exception when one pops more elements than there
      are on the stack. */
  void VM::pop_unsafe(unsigned int num_entries)
  {
    int new_size = stack.size() - num_entries;
    if (new_size < 0)
      throw out_of_range("Cannot pop more entries than are on the stack.");
    
    stack.resize(new_size);
  }

  /** Grabs an element on the stack. Throws an out_of_range
      error if the index is out of range. **/
  Value VM::get_stack_element(unsigned int stack_loc) const
  {
    try
    {
      return stack.at(ext_to_int_stack(stack_loc));
    }
    catch (out_of_range e)
    {
      ostringstream error_msg;
      error_msg << "cannot grab element " << stack_loc 
                << "on a stack of size " << stack.size();
      throw out_of_range(error_msg.str());
    }
  }

  /** Grabs the top elements off the stack. Throws an out_of_range
      error if there aren't enough elements to grab.
  */
  void VM::get_stack_elements(unsigned int num_elements,
                              vector<Value>::iterator result) const
  {
    if (num_elements + curr_sfp + VM::RETURN_ADDRESS_SIZE > stack.size())
    {
      ostringstream error_msg;
      error_msg << "stack only contains " << stack.size() << " elements";
      throw out_of_range(error_msg.str());
    }

    copy(stack.rbegin(), stack.rbegin() + num_elements, result);
  }

  /**
   * Grabs the top elements from the stack, stopping at the end of the stack if
   * necessary. This function ignores the frame pointer.
   */
  unsigned int VM::get_stack_elements_unsafe(unsigned int num_elements,
                                             vector<Value>::iterator result) const
  {
    const unsigned int num_to_get = min(num_elements, stack.size());
    copy(stack.rbegin(), stack.rbegin() + num_to_get, result);
    return num_to_get;
  }

  /** Grab the call arguments from the stack. Assumes that any
      element above the return address is a call argument
   */
  void VM::get_call_arguments(vector<Value>& args) const
  {
    if (curr_sfp < 0)
      throw out_of_range("no stack frame present");
    
    const int num_args = get_num_call_arguments();
    
    args.resize(num_args);
    get_stack_elements(num_args, args.begin());
  }

  int VM::get_num_call_arguments() const
  {
    return int_to_ext_stack(curr_sfp) - VM::RETURN_ADDRESS_SIZE + 1;
  }

  void VM::set_stack_element(unsigned int stack_loc, Value vr)
  {
    stack[ext_to_int_stack(stack_loc)] = vr;
  }

  void VM::execute(InstructionRef instr)
  {
    instr->execute(this);
  }

  /** pop the stack frame, used for returning from function calls **/
  void VM::pop_stack_frame()
  {
    if (curr_sfp < 0)
    {
      throw std::out_of_range("No existing function call to return from");
    }
    
    int old_sfp = curr_sfp;
    
    // move to the next stack frame pointer in the stack
    curr_sfp = stack[old_sfp].get_int();
    
    // load the return address into the instruction pointer
    ip = stack[old_sfp + 1].get_int();

    // pop the elements by resizing the stack
    stack.resize(old_sfp);
  }

  /** return the position of the stack frame, in external coordinates */
  int VM::get_stack_frame_location()
  {
    return int_to_ext_stack(curr_sfp);
  }

  /** pushes the current stack pointer and return address onto the stack */
  void VM::push_stack_frame(int ret_addr)
  {
    int old_stack_size = stack.size();

    // push back the current frame pointer and the return address
    stack.push_back(Value(curr_sfp));
    stack.push_back(Value(ret_addr));

    assert("stack frame size constant matches stack frame value" &&
           (stack.size() - old_stack_size == RETURN_ADDRESS_SIZE));

    curr_sfp = stack.size() - RETURN_ADDRESS_SIZE;
  }

  /** executes instructions until one of the stop conditions are reached.
      Execution halts whenever the ip runs over the valid range. Other
      conditions can also halt the program.
  */
  void VM::run(int flags)
  {
    running = true;

    InstructionRef curr_instr;

    try
    {
      // run until an instruction stops the program or
      // until we run over the instruction set
      while (running && is_valid_ip())
      {
        curr_instr = parent_actor->get_instruction(ip);
        
        if (debug_output)
        {
          cout << "Ip: " << ip;
          cout << ", " << "ss: " << stack.size();
          if (stack.size() > 0)
          {
            cout << ", s0: " << get_stack_element(0);
          }
          cout << ", acc: " << acc << endl;
        }

        ip++;
        execute(curr_instr);

        if ((flags & STOP_AT_RETURNS) && curr_instr->is_return())
          running = false;
        if (flags & STOP_AT_ASSERTIONS)
          return;
      }
    }
    catch (runtime_error e)
    {
      // stop the VM
      running = false;
      
      ostringstream error_msg;
      error_msg << "line " << curr_instr->get_line_number() << ": " 
                << e.what();

      // rethrow the exception
      throw runtime_error(error_msg.str());
    }

    if (!is_valid_ip())
      running = false;
  }

  bool VM::is_valid_ip() const 
  { 
    return parent_actor->is_valid_ip(ip); 
  }

  /** This method sets up the call stack for an executes a given function. It 
      makes no guarantees on the resulting VM state. It also returns the value
      returned by the function (i.e. the accumulator) */
  Value VM::call_function(string func_name, const vector<Value>& args, bool debug)
  {
    function_info func = parent_actor->get_function(func_name);
    
    if (func.addr == Actor::NO_LABEL)
      throw undefined_function_error(func_name + " is not a function in the program.");

    // set up the call stack
    push_stack_frame(-1);
    for (vector<Value>::const_reverse_iterator itr = args.rbegin(); itr != args.rend(); ++itr)
      push(*itr);
    
    // transfer control to the function
    ip = func.addr;

    // indicate on the call stack the we are in the function
    push_function_call(func_name, args.size(), true);
 
    // run the function
    if (debug)
      parent_actor->run_with_assertions();
    else
      run();

    // grab the return value
    return acc;
  }

  /**
   * pushes information about a function call onto the 
   * call stack. 
   * @param num_args number of arguments to copy as the functions parameters,
   * should be -1 to pass all arguments
   */
  void VM::push_function_call(string name, int num_args, bool local)
  {
    vector<Value> args;
    
    if (num_args < 0)
      // assume every element above the return address is an argument
      get_call_arguments(args);
    else
    {
      args.resize(num_args);
      // gets the requested number of arguments
      get_stack_elements(num_args, args.begin());
    }

    // fill the FCD structure
    function_call_data fcd(name, args.begin(), args.end(), local);
    
    call_stack.push_back(fcd);
  }

  void VM::pop_function_call()
  {
   if (call_stack.size() == 0)
      throw runtime_error("No function calls left to pop");
    call_stack.pop_back();
  }

  //! prints the value at each stack position
  void VM::stack_dump(ostream& o)
  {
    o << "accumulator: " << acc << endl;
    for (unsigned int i = 0; i < stack.size(); ++i)
    {
      o << "stack pos " << i << ": " << get_stack_element(i) << endl;
    }
  }

  //! grab the function associated with a particular name 
  VMFunction VM::get_function(string& s) const
  {
    if (!has_function(s))
      throw undefined_function_error("VM does not have function named " + s);
    return (*vm_function_map.find(s)).second;
  }

  //! add a function to the VM
  void VM::add_function(string name, VMFunction func)
  {
    vm_function_map[name] = func;
  }

  /*************************************
   * Call stack functions 
   ************************************/
  ostream& operator<< (ostream& o, const function_call_data& fci)
  {
    o << fci.func_name << '(';
    if (fci.args.size() > 0)
      fci.args[0].print(o, true);
    for (unsigned int i = 1; i < fci.args.size(); ++i)
    {
      o << ", ";
      fci.args[i].print(o, true);
    }
    o << ") (" << (fci.local_call ? "local" : "remote") << ')';
    return o;
  }

  void VM::print_call_stack(ostream& o)
  {
    for (vector<function_call_data>::iterator itr = call_stack.begin(); 
         itr != call_stack.end(); ++itr)
      o << *itr << endl;
  }

  /*************************************
   * function_call_data properties
   ***********************************/
  function_call_data::function_call_data(string _func, vector<Value>::iterator beg, 
                                     vector<Value>::iterator end, bool _local) :
    func_name(_func), args(beg, end), local_call(_local) 
  {
  }

}
      
